﻿using System;
using System.Collections.Generic;
using System.Text;

namespace TLang
{
    using Values;
    using Values.Primitives;

    public class Scope
    {
        private readonly Map<String, Map<String, Object>> _table = new Map<string, Map<string, object>>();
        private readonly Scope _parent;

        public static Scope BuildInitScope()
        {
            Scope init = new Scope();

            init.PutValue("+", new Add());
            init.PutValue("-", new Sub());
            init.PutValue("*", new Mult());
            init.PutValue("/", new Div());
            init.PutValue("%", new Mod());
            init.PutValue("^", new Pow());

            init.PutValue("<", new Lt());
            init.PutValue("<=", new LtE());
            init.PutValue(">", new Gt());
            init.PutValue(">=", new GtE());
            init.PutValue("=", new Eq());
            init.PutValue("!=", new NotEq());
            init.PutValue("and", new And());
            init.PutValue("or", new Or());
            init.PutValue("not", new Not());
            init.PutValue("len", new Len());

            init.PutValue("sha256", new Sha256());
            init.PutValue("ripemd160", new Ripemd160());
            init.PutValue("ecadd", new Ecadd());
            init.PutValue("ecmul", new Ecmul());
            init.PutValue("sign", new Sign());

            init.PutValue("is?", new Is());

            init.PutValue("str", new Str());
            init.PutValue("print", new Print());
            init.PutValue("typeof", new TypeOf());

            init.PutValue("void", VoidValue.Void);

            init.PutTypedValue("true", BoolValue.True, TType.Bool);
            init.PutTypedValue("false", BoolValue.False, TType.Bool);

            init.PutTypedValue("Int", TType.Int, TType.Int);
            init.PutTypedValue("Float", TType.Float, TType.Float);
            init.PutTypedValue("Bool", TType.Bool, TType.Bool);
            init.PutTypedValue("String", TType.String, TType.String);
            init.PutTypedValue("Vector", TType.Vector, TType.Vector);
            init.PutTypedValue("Record", TType.Vector, TType.Record);
            init.PutTypedValue("Fun", TType.Vector, TType.Fun);
            init.PutTypedValue("Void", TType.Any, TType.Void);
            init.PutTypedValue("Any", TType.Any, TType.Any);

            return init;
        }

        public Scope()
        {
            this._parent = null;
        }
        
        public Scope(Scope parent)
        {
            this._parent = parent;
        }
        
        public Scope Copy()
        {
            Scope ret = new Scope();
            foreach (String name in _table.Keys)
            {
                Map<String, Object> props = new Map<string, object>();
                props.PutAll(_table.Get(name));
                ret._table.Put(name, props);
            }
            return ret;
        }
        
        public void PutAll(Scope other)
        {
            foreach (String name in other._table.Keys)
            {
                Map<String, Object> props = new Map<string, object>();
                props.PutAll(other._table.Get(name));
                _table.Put(name, props);
            }
        }
        
        public Value Lookup(String name)
        {
            Object v = LookupProperty(name, "value");
            if (v == null)
            {
                return null;
            }
            else if (v is Value)
            {
                return (Value)v;
            }
            else
            {
                throw Util.GeneralError("value is not a Value, shouldn't happen: " + v);
            }
        }

        public Value LookupLocal(String name)
        {
            Object v = LookupPropertyLocal(name, "value");
            if (v == null)
            {
                return null;
            }
            else if (v is Value) {
                return (Value)v;
            } else {
                throw Util.GeneralError("value is not a Value, shouldn't happen: " + v);
            }
        }
        
        public TType LookupType(String name)
        {
            Object v = LookupProperty(name, "type");
            if (v == null)
            {
                return null;
            }
            else if (v is TType) {
                return (TType)v;
            } else {
                throw Util.GeneralError("value is not a Value, shouldn't happen: " + v);
            }
        }
        
        public TType LookupLocalType(String name)
        {
            Object v = LookupPropertyLocal(name, "type");
            if (v == null)
            {
                return null;
            }
            else if (v is TType) {
                return (TType)v;
            } else {
                throw Util.GeneralError("value is not a Value, shouldn't happen: " + v);
            }
        }
        
        public Object LookupPropertyLocal(String name, String key)
        {
            Map<String, Object> item = _table.Get(name);
            if (item != null)
            {
                return item.Get(key);
            }
            else
            {
                return null;
            }
        }
        
        public Object LookupProperty(String name, String key)
        {
            Object v = LookupPropertyLocal(name, key);
            if (v != null)
            {
                return v;
            }
            else if (_parent != null)
            {
                return _parent.LookupProperty(name, key);
            }
            else
            {
                return null;
            }
        }
        
        public Map<String, Object> LookupAllProps(String name)
        {
            return _table.Get(name);
        }
        
        public Scope FindDefiningScope(String name)
        {
            Object v = _table.Get(name);
            if (v != null)
            {
                return this;
            }
            else if (_parent != null)
            {
                return _parent.FindDefiningScope(name);
            }
            else
            {
                return null;
            }
        }
        
        public void Put(String name, String key, Object value)
        {
            Map<String, Object> item = _table.Get(name);
            if (item == null)
            {
                item = new Map<string, object>();
            }
            item.Put(key, value);
            _table.Put(name, item);
        }
        
        public void PutProperties(String name, Map<String, Object> props)
        {
            Map<String, Object> item = _table.Get(name);
            if (item == null)
            {
                item = new Map<string, object>();
            }
            item.PutAll(props);
            _table.Put(name, item);
        }
        
        public void PutValue(String name, Value value)
        {
            Put(name, "value", value);
        }
        
        public void PutType(String name, TType type)
        {
            Put(name, "type", type);
        }

        public void PutTypedValue(string name, Value value, TType type)
        {
            Put(name, "value", value);
            Put(name, "type", type);
        }

        public ICollection<string> KeysLocal
        {
            get { return _table.Keys; }
        }

        public bool ContainsKeyLocal(String key)
        {
            return _table.ContainsKey(key);
        }
        
        public override String ToString()
        {
            StringBuilder sb = new StringBuilder();
            foreach (String name in _table.Keys)
            {
                sb.Append(Constants.SquareBegin).Append(name).Append(" ");
                foreach (var e in _table.Get(name))
                {
                    sb.Append(Constants.ColonChar + e.Key + " " + e.Value);
                }
                sb.Append(Constants.SquareEnd);
            }
            return sb.ToString();
        }
    }
}
